
def run(func, args, num_tasks=None, max_wait=600):
	import threading, time
	from . import color
	from tqdm import tqdm
	total_tasks = len(args)
	if num_tasks is None or num_tasks > total_tasks:
		num_tasks = total_tasks
	lock = threading.Lock()
	pbar = tqdm(total=total_tasks)
	res = [None for a in args]
	next_index = 0
	def thread_func():
		nonlocal next_index
		try:
			while next_index < total_tasks:
				index = -1
				with lock:
					if next_index < total_tasks:
						index = next_index
						next_index += 1
				if index != -1:
					pbar.update(1)
					res[index] = func(args[index])
		except:
			import traceback
			color.text(traceback.format_exc(), 'red')
	threads = []
	for i in range(num_tasks):
		thread = threading.Thread(target=thread_func, args=())
		threads.append(thread)
		thread.start()
	ex = False
	try:
		while next_index < total_tasks:
			time.sleep(1)
		pbar.close()
		wait_start = time.time()
		pbar = tqdm(total=num_tasks)
		while (num_tasks > 0) and (time.time() - wait_start < max_wait):
			for i, thread in enumerate(threads):
				if (thread is not None) and (not thread.is_alive()):
					thread.join()
					threads[i] = None
					num_tasks -= 1
					pbar.update(1)
			time.sleep(1)
		if num_tasks > 0:
			pbar.update(num_tasks)
			color.text(f'# threads remain alive: {num_tasks}', 'red')
		pbar.close()
	except:
		import traceback
		color.text(traceback.format_exc(), 'red')
		ex = True
	return res, ex
